﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace System
{
	public static class TypeExtension
	{
		public static readonly object[] nullPrms = new object[] { };
		private readonly static Dictionary<string, Type> typeNameIncedents = new Dictionary<string, Type>();
		private readonly static Dictionary<Type, string> nameTypeIncedents = new Dictionary<Type, string>();
		private readonly static Dictionary<KeyValuePair<string, int>, Type> typeNameIncedentsSubclasses = new Dictionary<KeyValuePair<string, int>, Type>();

		public static object CreateInstance(this Type t)
		{
			return Activator.CreateInstance(t);
			//var c = t.GetConstructor(Type.EmptyTypes);
			//if (c != null)
			//    return c.Invoke(TypeExtension.nullPrms);
			//else
			//    return null;
		}

		public static T CreateInstance<T>(this Type t)
			where T : class
		{
			return (T)t.CreateInstance();
		}

		public static T GetDefault<T>()
		{
			return default(T);
		}

		public static bool IsDefault<T>(this T obj)
		{
			return Equals(obj, default(T));
		}

		/// <summary>
		/// Указывает на первую квадратную скобку имени типа, принадлежащую данному типу непосредственно
		/// </summary>
		/// <param name="type"></param>
		/// <returns></returns>
		private static int GetGenericDelimBegin(Type type)
		{
			string nm = type.FullName;
			int cntDelim = 0;
			bool started = false;
			for (int i = nm.Length - 1; i >= 0; i--)
			{
				if (nm[i] == ']')
				{
					cntDelim++;
					started = true;
				}
				else if (nm[i] == '[')
					cntDelim--;
				if (cntDelim == 0 && started)
					return i;
			}
			return -1;
		}

		/// <summary>
		/// Получение имени типа (с квадратными скобками для прототипов) без вызова исключения в случае ошибки
		/// </summary>
		/// <param name="type"></param>
		/// <param name="typeAttribute"></param>
		/// <param name="inherit"></param>
		/// <returns></returns>
		public static string GetGenericName(this Type type)
		{
			if (type != null)
			{
				string st;
				if (!nameTypeIncedents.TryGetValue(type, out st))
				{
					st = type.FullName;
					StringBuilder sb = new StringBuilder();
					if (type.IsGenericType)
					{
						st = st.Substring(0, GetGenericDelimBegin(type));
						int dotPos = st.LastIndexOf('.'),
							lmPos = st.LastIndexOf('`');
						sb.Append(st.Substring(dotPos + 1, lmPos - dotPos - 1).Replace('+', '.'));
						sb.Append('[');
						foreach (var item in type.GetGenericArguments())
						{
							sb.Append(item.GetGenericName());
							sb.Append(',');
						}
						sb[sb.Length - 1] = ']';
					}
					else
					{
						sb.Append(st
							.Substring(st.LastIndexOf('.') + 1)
							.Replace('+', '.'));
					}
					nameTypeIncedents.AddOrSetValue(type, st = sb.ToString());
				}
				return st;
			}
			else
				return "";
		}

		public static Type GetTypeFromNameAndCompatible<T>(string typeName, Func<Type[], Type> resolveFunc)
		{
			return GetTypeFromNameCompatible(typeName, resolveFunc, typeof(T));
		}

		public static IEnumerable<Type> GetAllLoaded()
		{
			return AppDomain.CurrentDomain.GetAssemblies().SelectMany(s =>
			{
				try
				{
					return s.GetTypes();
				}
				catch
				{
					return new Type[] { };
				}
			});
		}

		public static Type GetTypeFromNameCompatible(string typeName, Func<Type[], Type> resolveFunc, params Type[] typeConstraints)
		{
			Type res = null;
			typeConstraints = typeConstraints ?? Type.EmptyTypes;
			int HashConstraints = (typeConstraints.Length != 0) ? typeConstraints.Select(s => s.GetHashCode()).Aggregate((total, next) => total + next) : 0;
			var kv = new KeyValuePair<string, int>(typeName, HashConstraints);
			if (!typeName.IsEmpty() && !(typeName = typeName.Trim()).IsEmpty())
				if (!typeNameIncedents.TryGetValue(typeName, out res) && (HashConstraints == 0 || !typeNameIncedentsSubclasses.TryGetValue(kv, out res)))
				{
					res = typeConstraints
							.Where(s => GetGenericName(s) == typeName)
							.FirstOrDefault(s => typeConstraints.All(s1 => s1.IsAssignableFrom(s)));
					if (res == null)
						if (typeName.Contains('['))
						{
							if (IsBalancedDelim(typeName, '[', ']').IfFalse(() => new Exception("Несбалансированные скобки в выражении: {0}".goFormat(typeName)))
								&& (typeName[0] != '[').IfFalse(() => new Exception("Имя типа не должно начинаться с угловой скобки"))
								&& (typeName[typeName.Length - 1] == ']').IfFalse(() => new Exception("Имя прототипа должно заканчиваться угловой скобкой")))
								res = GetGenericType(typeName, resolveFunc);
						}
						else
						{
							string nm = '.' + typeName.Replace('.', '+');
							var q = GetAllLoaded();
							if (HashConstraints != 0)
								q = q.Where(s =>
									typeConstraints.All(t => t.IsAssignableFrom(s))
									);
							res = q.FirstOrDefault(s => s.FullName.EndsWith(nm));
						}
					if (res != null)
					{
						if (HashConstraints == 0)
						{
							if (!typeNameIncedents.ContainsKey(typeName))
								typeNameIncedents.Add(typeName, res);
						}
						else if (!typeNameIncedentsSubclasses.ContainsKey(kv))
							typeNameIncedentsSubclasses.Add(kv, res);
					}
					else if (resolveFunc != null)
						res = resolveFunc(typeConstraints);
				}
			(res != null).IfFalse(() => new Exception("Не найден тип: {0}{1}".goFormat(typeName
				, HashConstraints != 0 ? ", совместимый с {0}".goFormat(typeConstraints.Select(s => s.Name).Enumerate()) : "")));
			return res;
		}

		public static Type GetTypeFromName(string typeName)
		{
			return GetTypeFromNameCompatible(typeName, null);
		}

		/// <summary>
		/// Контракт: имя типа содержит баланс квадратных скобок и его длина больше 0
		/// </summary>
		/// <param name="typeName"></param>
		/// <returns></returns>
		private static Type GetGenericType(string typeName, Func<Type[], Type> resolveFunc)
		{
			List<string> innerTypeNames = new List<string>();
			string ss;
			int iN = typeName.IndexOf('[');
			int i = iN + 1, iB, iD = 1;
			for (iB = i; i < typeName.Length; i++)
			{
				switch (typeName[i])
				{
					case '[': iD++; break;
					case ']': iD--;
						if (iD == 0)
							goto case ',';
						else
							break;
					case ',':
						if (iD <= 1)
						{
							//innerType.Add(GetTypeFromName(ss = typeName.Substring(iB, i - iB)));
							//(innerType.Last() != null).IfFalse(() => new Exception("Не найден тип: {0}".goFormat(ss)));
							innerTypeNames.Add(typeName.Substring(iB, i - iB).Trim());
							iB = i + 1;
						}
						break;
				}
			}
			Type[] innerType = new Type[innerTypeNames.Count];
			Type res = GetTypeFromName(ss = "{0}`{1}".goFormat(typeName.Substring(0, iN), innerTypeNames.Count));
			var gTypes = res.GetGenericArguments();
			for (i = 0; i < innerTypeNames.Count; i++)
			{
				innerType[i] = GetTypeFromNameCompatible(innerTypeNames[i], resolveFunc, gTypes[i].GetGenericParameterConstraints());
			}
			if ((res != null).IfFalse(() => new Exception("Не найден класс-протип: {0}".goFormat(ss))))
				res = res.MakeGenericType(innerType);

			return res;
		}

		public static bool TryGetTypeFromName(string typeName, out Type t)
		{
			t = null;
			try
			{
				t = GetTypeFromName(typeName);
			}
			catch
			{
				return false;
			}
			return true;
		}

		private static bool IsBalancedDelim(string strk, char openDelim, char closeDelim)
		{
			int i = 0;
			if (!strk.IsEmpty())
				if (openDelim == closeDelim)
					i = strk.Count(openDelim) % 2;
				else
					foreach (var c in strk)
					{
						i = c == openDelim ? i + 1 :
								c == closeDelim ? i - 1 : i;
						if (i < 0) break;
					}
			return i == 0;
		}

		public static object GetAttributeOfType(this Type type, Type typeAttribute, bool inherit)
		{
			if (type != null)
				return type.GetCustomAttributes(typeAttribute, inherit).FirstOrDefault();
			else
				return null;
		}

		public static T GetAttribute<T>(this Type type, bool inherit)
			where T : Attribute
		{
			if (type != null)
				return (T)type.GetCustomAttributes(typeof(T), inherit).FirstOrDefault();
			else
				return null;
		}

		public static IEnumerable<PropertyInfo> GetPropertiesWithAttribute(this Type type, Type attribute)
		{
			return type.GetPropertiesWithAttribute(attribute, true);
		}

		public static IEnumerable<PropertyInfo> GetPropertiesWithAttribute(this Type type, Type attribute, bool inherit)
		{
			if (type != null && attribute != null)
				foreach (var item in type.GetProperties().Where(s => s.IsDefined(attribute, inherit)))
					yield return item;
		}

		public static IEnumerable<PropertyInfo> GetPropertiesWithAttribute<T>(this Type type, bool inherit)
			where T : Attribute
		{
			if (type != null)
				foreach (var item in type.GetProperties().Where(s => s.IsDefined(typeof(T), inherit)))
					yield return item;
		}

		public static IEnumerable<KeyValuePair<PropertyInfo, T>> GetPropertiesAndAttribute<T>(this Type type, bool inherit)
			where T : Attribute
		{
			if (type != null)
				foreach (var item in type.GetProperties()
					.Select(s => new KeyValuePair<PropertyInfo, T>(s, s.GetAttribute<T>(inherit)))
					.Where(s => s.Value != null))
					yield return item;
		}

		public static IEnumerable<PropertyInfo> GetPropertiesWithAttribute<T>(this Type type)
			where T : Attribute
		{
			return GetPropertiesWithAttribute<T>(type, true);
		}

		public static IEnumerable<EventInfo> GetEventsWithAttribute<T>(this Type type, bool inherit)
			where T : Attribute
		{
			if (type != null)
				foreach (var item in type.GetEvents().Where(s => s.IsDefined(typeof(T), inherit)))
					yield return item;
		}

		public static MethodInfo GetMethodWithAttribute(this Type type, Type attribute, bool inherit)
		{
			return type.GetMethods().FirstOrDefault(s => s.IsDefined(attribute, inherit));
		}

		public static IEnumerable<MethodInfo> GetMethodsWithAttribute<Tattr>(this Type type, bool inherit)
			where Tattr : Attribute
		{
			return type.GetMethods().Where(s => s.IsDefined(typeof(Tattr), inherit));
		}

		public static IEnumerable<KeyValuePair<MethodInfo, Tattr>> GetMethodsAndAttribute<Tattr>(this Type type, bool inherit)
			where Tattr : Attribute
		{
			if (type != null)
				foreach (var item in type.GetMethods()
					.Select(s => new KeyValuePair<MethodInfo, Tattr>(s, s.GetAttribute<Tattr>(inherit)))
					.Where(s => s.Value != null))
					yield return item;
		}

		public static MethodInfo GetMethodWithAttribute<Tattr>(this Type type, bool inherit)
		{
			return type.GetMethodWithAttribute(typeof(Tattr), inherit);
		}
		public static MethodInfo GetMethodWithAttribute<Tattr>(this Type type)
		{
			return type.GetMethodWithAttribute(typeof(Tattr), true);
		}



		static TypeExtension()
		{
			typeNameIncedents.Add("object", typeof(object));

			typeNameIncedents.Add("char", typeof(char));
			typeNameIncedents.Add("string", typeof(string));

			typeNameIncedents.Add("sbyte", typeof(sbyte));
			typeNameIncedents.Add("short", typeof(short));
			typeNameIncedents.Add("ushort", typeof(ushort));
			typeNameIncedents.Add("byte", typeof(byte));
			typeNameIncedents.Add("int", typeof(int));
			typeNameIncedents.Add("uint", typeof(uint));
			typeNameIncedents.Add("ulong", typeof(ulong));
			typeNameIncedents.Add("long", typeof(long));

			typeNameIncedents.Add("double", typeof(double));
			typeNameIncedents.Add("decimal", typeof(decimal));
			typeNameIncedents.Add("float", typeof(float));

			typeNameIncedents.Add("date", typeof(DateTime));
			typeNameIncedents.Add("time", typeof(DateTime));

			typeNameIncedents.Add("bool", typeof(bool));
		}
	}
}
